# app/services/token_service.py
import logging
from datetime import datetime, timedelta, timezone
from typing import Any, Dict

from jose import JWTError
from redis.asyncio import Redis

from app.core.config import Settings
from app.schemas.auth import Token
from app.utils.jwt_utils import JWTUtils

logger = logging.getLogger(__name__)


class TokenService:
    """
    令牌服务：JWT 生成、刷新、黑名单管理（支持用户级和令牌级吊销）
    """

    def __init__(self, redis: Redis, settings: Settings):
        self.redis = redis
        self.settings = settings
        self.jwt_utils = JWTUtils(
            secret_key=settings.SECRET_KEY,
            algorithm=settings.ALGORITHM,
            access_token_lifetime=settings.ACCESS_TOKEN_LIFETIME,
            refresh_token_lifetime=settings.REFRESH_TOKEN_LIFETIME,
        )
        self._blacklist_prefix = f"{settings.KEY_AUTH_TOKEN}:blacklist"

    @property
    def blacklist_prefix(self) -> str:
        """获取黑名单前缀"""
        return self._blacklist_prefix

    def _get_remaining_ttl(self, token: str) -> int:
        """计算令牌剩余有效期（秒）"""
        try:
            payload = self.jwt_utils.decode_token(token)
            exp = payload.get("exp")
            if exp is None:
                return 0
            now = int(datetime.now(timezone.utc).timestamp())
            return max(0, exp - now)
        except Exception as e:
            logger.error(f"解析令牌失败: {e}", exc_info=True)
            return 0

    async def add_token_to_blacklist(self, token: str) -> bool:
        """吊销单个令牌（access 或 refresh）"""
        try:
            payload = self.jwt_utils.decode_token(token)
            jti: str = payload.get("jti")
            token_type: str = payload.get("type")
            if not jti or not token_type:
                logger.warning("令牌缺少 jti 或 type，无法加入黑名单")
                return False

            ttl = self._get_remaining_ttl(token)
            if ttl <= 0:
                return True  # 已过期，无需加入

            key = f"{self._blacklist_prefix}:{token_type}:{jti}"
            await self.redis.set(key, "1", ex=ttl)
            logger.debug(f"令牌 {jti} ({token_type}) 已加入黑名单，TTL={ttl}s")
            return True

        except JWTError:
            logger.warning("无效令牌，无法加入黑名单")
            return False
        except Exception as e:
            logger.error(f"吊销令牌失败: {e}", exc_info=True)
            return False

    async def is_token_blacklisted(self, token: str) -> bool:
        """检查令牌是否在黑名单中"""
        try:
            payload = self.jwt_utils.decode_token(token)
            jti: str = payload.get("jti")
            token_type: str = payload.get("type")
            if not jti or not token_type:
                return False

            key = f"{self._blacklist_prefix}:{token_type}:{jti}"
            return bool(await self.redis.exists(key))

        except Exception as e:
            logger.error(f"检查令牌黑名单失败: {e}", exc_info=True)
            return False

    async def add_user_to_blacklist(self, user_id: str) -> bool:
        """吊销用户所有令牌"""
        if not user_id:
            raise ValueError("user_id 不能为空")

        try:
            key = f"{self._blacklist_prefix}:user:{user_id}"
            ttl = timedelta(days=self.settings.USER_BLACKLIST_TTL).seconds  # 秒，建议 30 天 = 2592000
            await self.redis.set(key, "1", ex=ttl)
            logger.info(f"用户 {user_id} 已加入全局黑名单，TTL={ttl}s")
            return True
        except Exception as e:
            logger.error(f"吊销用户令牌失败: {e}", exc_info=True)
            return False

    async def is_user_blacklisted(self, user_id: str) -> bool:
        """检查用户是否在全局黑名单"""
        if not user_id:
            return False
        key = f"{self._blacklist_prefix}:user:{user_id}"
        return bool(await self.redis.exists(key))

    async def is_token_valid(self, token: str) -> bool:
        """
        综合验证令牌有效性：
        - 未被吊销（令牌级或用户级）
        - JWT 签名有效
        """
        try:
            payload = self.jwt_utils.decode_token(token)
            user_id: str = payload.get("sub")
            if not user_id:
                return False

            if await self.is_user_blacklisted(user_id):
                return False

            if await self.is_token_blacklisted(token):
                return False

            return True
        except Exception as e:
            logger.error(f"令牌验证异常: {e}", exc_info=True)
            return False

    async def create_tokens(self, data: Dict[str, Any]) -> Token:
        """生成 access_token 和 refresh_token"""
        if "sub" not in data:
            raise ValueError("payload 必须包含 'sub' 字段（用户标识）")

        token_info = self.jwt_utils.create_tokens(data)
        payload_access = self.jwt_utils.decode_access_token(token_info["access_token"])
        # payload_refresh = self.jwt_utils.decode_refresh_token(token_info["refresh_token"])

        # async with self.redis.pipeline(transaction=True) as pipe:
        #     # 存储 access_token
        #     pipe.set(
        #         f"{self.settings.KEY_ACCESS_TOKEN_USER}:{payload_access['jti']}",
        #         payload_refresh["jti"],
        #         ex=payload_access["exp"] - int(datetime.now(timezone.utc).timestamp()),
        #     )
        #     # 存储 refresh_token
        #     pipe.set(
        #         f"{self.settings.KEY_REFRESH_TOKEN_USER}:{payload_refresh['jti']}",
        #         payload_access["jti"],
        #         ex=payload_refresh["exp"] - int(datetime.now(timezone.utc).timestamp()),
        #     )
        #     await pipe.execute()

        # ✅ 符合 OAuth2 规范：expires_in 是剩余秒数，不是时间戳
        now = int(datetime.now(timezone.utc).timestamp())
        expires_in = payload_access["exp"] - now

        return Token(
            access_token=token_info["access_token"],
            refresh_token=token_info["refresh_token"],
            expires_in=expires_in,
        )

    async def refresh_token(self, refresh_token: str) -> Token:
        """
        刷新令牌：
        1. 验证 refresh_token 有效且未吊销
        2. 生成新 tokens
        3. 吊销旧 refresh_token（防止重放）
        """
        # 1. 验证 refresh_token 本身有效
        if not await self.is_token_valid(refresh_token):
            raise ValueError("无效或已吊销的刷新令牌")

        payload = self.jwt_utils.decode_refresh_token(refresh_token)

        # 2. 提取必要字段（只保留安全字段）
        user_id = payload.get("sub")
        if not user_id:
            raise ValueError("刷新令牌缺少用户标识")

        # 可选：保留其他安全 claim（如 role），但需谨慎
        SAFE_CLAIMS = {"sub", "user_id", "role"}
        safe_claims = {k: v for k, v in payload.items() if k in SAFE_CLAIMS}

        # 3. 生成新令牌
        new_tokens = await self.create_tokens(safe_claims)

        # 4. 吊销旧 refresh_token（防止重复使用）
        await self.add_token_to_blacklist(refresh_token)

        return new_tokens

    def decode_access_token(self, token: str) -> Dict[str, Any]:
        """解码 access_token，返回 payload 字典"""
        return self.jwt_utils.decode_access_token(token)

    def decode_refresh_token(self, token: str) -> Dict[str, Any]:
        """解码 refresh_token，返回 payload 字典"""
        return self.jwt_utils.decode_refresh_token(token)
